/** @file    Appliance.cpp
 *  @author  Alessandro Polo
 *  @version $Id: Appliance.cpp 2829 2010-08-02 20:45:47Z alex $
 *  @brief
 * File containing methods for the wosh::building::Appliance class.
 * The header for this class can be found in Appliance.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include <framework/building/Appliance.h>

 #include <core/ObjectAllocator.h>
 #include <core/List.h>
 #include <core/MethodsCommon.h>


namespace wosh {
 namespace building {

 WOSH_REGISTER(wosh::building::Appliance, "wosh::building::BuildingObject", _Appliance_VERSION, _static_Appliance )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

Appliance::Appliance()
	: BuildingObject() {
	BuildingObject::setName( Utilities::format("wosh.building.Appliance#%ld", Utilities::randomLong()) );
 }

void Appliance::init() {
	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _Appliance_KEY_ExternalAppliance, false, Permission(Permission::RW, Permission::RW, Permission::Read) );
	Properties.set( _Appliance_KEY_PowerWatt, false, Permission(Permission::RW, Permission::RW, Permission::Read) );

	this->state = Appliance::APPLIANCE_UNKNOWN; this->level = -1;
	setState(Appliance::APPLIANCE_UNKNOWN, -1, false);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodWRESULTwSecurityToken* mmTurnOn = Methods.create<MethodWRESULTwSecurityToken>( _Appliance_METHOD_on, "turn appliance on", Permission::R_XR_XR__() );
	mmTurnOn->setMethod( this, (MethodWRESULTwSecurityTokenPtr)&Appliance::mmDoTurnOn );

	MethodWRESULTwSecurityToken* mmTurnOff = Methods.create<MethodWRESULTwSecurityToken>( _Appliance_METHOD_off, "turn appliance off", Permission::R_XR_XR__() );
	mmTurnOff->setMethod( this, (MethodWRESULTwSecurityTokenPtr)&Appliance::mmDoTurnOff );

	MethodMessageResponse* mmFade = Methods.create<MethodMessageResponse>( _Appliance_METHOD_fade, "fade appliance", Permission::R_XR_XR__() );
	mmFade->setMethod( this, (MethodMessageResponsePtr)&Appliance::mmDoFade );

	MethodMessageResponse* mmDim = Methods.create<MethodMessageResponse>( _Appliance_METHOD_dim, "dim appliance", Permission::R_XR_XR__() );
	mmDim->setMethod( this, (MethodMessageResponsePtr)&Appliance::mmDoDim );

	MethodMessageResponse* mmBright = Methods.create<MethodMessageResponse>( _Appliance_METHOD_bright, "bright appliance", Permission::R_XR_XR__() );
	mmBright->setMethod( this, (MethodMessageResponsePtr)&Appliance::mmDoBright );

	BuildingObject::init();
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////// PROCESS CHILD MESSAGES

void Appliance::busMessageChild( const Message& childMessage ) {
	if ( childMessage.isEmpty() ) return;
	const Variant* data = NULL;
	if ( childMessage.getContent()->isResponse() ) {
		const Response* response = childMessage.getContent()->asResponse();
		if ( response == NULL ) return;
		if ( !response->hasData() ) return;
		if ( !response->getData()->isKindOf<Property>() ) return;
		const Property* prop = dynamic_cast<const Property*>(response->getData());
		if ( prop == NULL ) return;
		if ( prop->getKey() != _Device_KEY_DeviceStatus ) return;
		data = &prop->getValue();
	 }
	else if ( childMessage.getContent()->isFact() ) {
		const Fact* fact = childMessage.getContent()->asFact();
		if ( fact == NULL ) return;
		if ( fact->getEvent() != _Device_EVENT_StateChanged ) {
			Log(LOG_WARNING, ":busMessageChild(%s) UNKNOWN Event '%s'", childMessage.getSource().getName().c_str(), fact->getEvent().c_str() );
			return;
		 }
		if ( !fact->hasData() ) {
			Log(LOG_WARNING, ":busMessageChild(%s) Event '"_Device_EVENT_StateChanged"' INVALID, no Content!", childMessage.getSource().getName().c_str() );
			return;
		 }
		if ( fact->getData()->isKindOf<Variant>() )
			data = dynamic_cast<const Variant*>( fact->getData() );
			
		if ( data == NULL ) {
			Log(LOG_WARNING, ":busMessageChild(%s) EVENT '"_Device_EVENT_StateChanged"' Invalid, Not Data-Content! [%s]", childMessage.getSource().getName().c_str(), fact->getData()->getClassName() );
			return;
		 }
	}
	else { // ignoring requests, ..
		return;
	 }
	if ( data == NULL )
		return;
	if ( !data->isNumeric() ) {
		Log(LOG_WARNING, ":busMessageChild(%s) NOT-INT [%s] Switch-State '%s'", childMessage.getSource().getName().c_str(), data->getTypeAsString(), data->toString().c_str() );
		return;
	 }
	int devValue = data->toInteger(-1);
	if ( devValue < 0 || devValue > 100 ) {
		Log(LOG_WARNING, ":busMessageChild(%s) OutOfRange Switch-State [%d]", childMessage.getSource().getName().c_str(), devValue );
	 }
	Log(LOG_VERBOSE, ":busMessageChild(%s) Switch-State => %d", childMessage.getSource().getName().c_str(), devValue );

	if ( devValue == 0 )
		setState(Appliance::APPLIANCE_OFF, devValue, true, childMessage.getSecurityToken());
	else if ( devValue == 100 )
		setState(Appliance::APPLIANCE_ON, devValue, true, childMessage.getSecurityToken());
	else
		setState(Appliance::APPLIANCE_FADED, devValue, true, childMessage.getSecurityToken());
 }

////////////////////////////////////////////////////////////////////////////////////// PROCESS CHILD MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS

void Appliance::setState( Appliance::APPLIANCE_STATE new_state, int new_level, bool raise_event, const SecurityToken* source_token ) {
	Appliance::APPLIANCE_STATE old_state = this->state;
	int old_level = this->level;
	Properties.transactionBeginWrite();
	if ( this->state != new_state || this->level != new_level ) {
		this->state = new_state;
		this->level = new_level;
		Properties.update( _BuildingObject_KEY_State, getStateAsString() );
	 }
	Properties.update( _BuildingObject_KEY_StateTs, Variant(Utilities::std_time(), Variant::DATETIME) );
	Properties.transactionEnd();
	if ( this->raiseAlwaysEvents || raise_event ) {
		Log(LOG_VERBOSE, ":setState(%s,%d) Raising Event.. ", getStateAsString(), new_level );
		BuildingObject::raiseEvent( new Appliance_StateChanged( new_state, new_level, old_state, old_level ), source_token );
	 }
	else
		Log(LOG_DEBUG, ":setState(%s,%d) Not Raising Event.", getStateAsString(), new_level );
 }

//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Appliance::turn( int value, const SecurityToken* security ) {
	Log(LOG_INFO, ":turn(%d)", value );
	// state update is asynch. raised by Child-Device (as Fact)
	Request* request = new Request(_Dimmer_METHOD_set, new Variant(value) );
	request->setOmitResponse(true);
	BuildingObject::controlChild( getMainSwitchName(), request, security );
	return WRET_OK;
 }

WRESULT Appliance::dim( int value, const SecurityToken* security ) {
	Log(LOG_INFO, ":dim(%d)", value );
	if ( value <= 0 )
		value = 4;
	// state update is asynch. raised by Child-Device (as Fact)
	Request* request = new Request(_Dimmer_METHOD_dim, new Variant(value) );
	request->setOmitResponse(true);
	BuildingObject::controlChild( getMainSwitchName(), request, security );
	return WRET_OK;
 }

WRESULT Appliance::bright( int value, const SecurityToken* security ) {
	Log(LOG_INFO, ":bright(%d)", value );
	if ( value <= 0 )
		value = 4;
	// state update is asynch. raised by Child-Device (as Fact)
	Request* request = new Request(_Dimmer_METHOD_bright, new Variant(value) );
	request->setOmitResponse(true);
	BuildingObject::controlChild( getMainSwitchName(), request, security );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// DEVICE METHODS

WRESULT Appliance::mmDoTurnOn( const SecurityToken* security ) {
	Log(LOG_INFO, ":mmDoTurnOn()" );
	// state update is asynch. raised by Child-Device (as Fact)
	Request* request = new Request(_Switch_METHOD_on);
	request->setOmitResponse(true);
	BuildingObject::controlChild( getMainSwitchName(), request, security );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT Appliance::mmDoTurnOff( const SecurityToken* security ) {
	Log(LOG_INFO, ":mmDoTurnOff()" );
	// state update is asynch. raised by Child-Device (as Fact)
	Request* request = new Request(_Switch_METHOD_off);
	request->setOmitResponse(true);
	BuildingObject::controlChild( getMainSwitchName(), request, security );
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* Appliance::mmDoFade( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	int value = request->getArgument().toInteger(-10000);
	Log(LOG_INFO, ":mmDoFade(%d)", value );

	WRESULT ret = turn( value, requestMessage->getSecurityToken() );
	return request->replyResponse(ret);
 }

Response* Appliance::mmDoDim( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	int value = request->getArgument().toInteger(-10000);
	Log(LOG_INFO, ":mmDoDim(%d)", value );

	WRESULT ret = dim( value, requestMessage->getSecurityToken() );
	return request->replyResponse(ret);
 }

Response* Appliance::mmDoBright( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	int value = request->getArgument().toInteger(-10000);
	Log(LOG_INFO, ":mmDoBright(%d)", value );

	WRESULT ret = bright( value, requestMessage->getSecurityToken() );
	return request->replyResponse(ret);
 }

////////////////////////////////////////////////////////////////////////////////////////////// DEVICE METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool Appliance::updateHash( DataHashContext* hash_context ) const {
	if ( hash_context == NULL ) return false;
	bool ret = BuildingObject::updateHash( hash_context );
	ret = hash_context->update( this->state ) & ret;
	ret = hash_context->update( this->level ) & ret;
	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const char* ApplianceStateStr[] = {
	"OFF",		// (APPLIANCE_STATE)0
	"ON",		// (APPLIANCE_STATE)100
	"FADED",	// (APPLIANCE_STATE)101
	"UNKNOWN"	// (APPLIANCE_STATE)102
 };

Appliance::APPLIANCE_STATE Appliance::getStateFromString( const char* state ) {
	if ( state == NULL ) return Appliance::APPLIANCE_UNKNOWN;
	if ( strcmp( state, ApplianceStateStr[0]) == 0 )
		return Appliance::APPLIANCE_OFF;
	if ( strcmp( state, ApplianceStateStr[1]) == 0 )
		return Appliance::APPLIANCE_ON;
	if ( strcmp( state, ApplianceStateStr[2]) == 0 )
		return Appliance::APPLIANCE_FADED;
	return Appliance::APPLIANCE_UNKNOWN;
 }

const char* Appliance::getStateAsString( Appliance::APPLIANCE_STATE state ) {
	switch(state) {
		case Appliance::APPLIANCE_OFF:		return ApplianceStateStr[0];
		case Appliance::APPLIANCE_ON:		return ApplianceStateStr[1];
		case Appliance::APPLIANCE_FADED:	return ApplianceStateStr[2];
		case Appliance::APPLIANCE_UNKNOWN:
		default:							return ApplianceStateStr[3];
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Appliance::APPLIANCE_STATE intToApplianceStateEnum( int value ) {
	if ( value >= Appliance::APPLIANCE_UNKNOWN ) return Appliance::APPLIANCE_UNKNOWN;
	if ( value < Appliance::APPLIANCE_OFF ) return Appliance::APPLIANCE_UNKNOWN;
	return (Appliance::APPLIANCE_STATE)value;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 WOSH_REGISTER(wosh::building::Appliance_StateChanged, "wosh::Fact", 1.0, _static_Appliance_StateChanged )

Appliance_StateChanged::Appliance_StateChanged( Appliance::APPLIANCE_STATE current_appliance_state )
	: wosh::Fact( _BuildingObject_EVENT_StateChanged ) {
	setData( new Variant( (int)current_appliance_state ) );
 }

Appliance_StateChanged::Appliance_StateChanged( Appliance::APPLIANCE_STATE current_appliance_state, int current_level )
	: wosh::Fact( _BuildingObject_EVENT_StateChanged ) {
	List* args = List::createFrom<Variant>( (int)current_appliance_state, (int)current_level );
	setData(args);
 }

Appliance_StateChanged::Appliance_StateChanged( Appliance::APPLIANCE_STATE current_appliance_state, int current_level,
												Appliance::APPLIANCE_STATE previous_appliance_state, int previous_level )
	: wosh::Fact( _BuildingObject_EVENT_StateChanged ) {
	List* list = new List(4);
	list->push_back<Variant>( (int)current_appliance_state );
	list->push_back<Variant>( (int)current_level );
	list->push_back<Variant>( (int)previous_appliance_state );
	list->push_back<Variant>( (int)previous_level );
	setData(list);
 }

Appliance::APPLIANCE_STATE Appliance_StateChanged::getApplianceState() const {
	const Variant* var = getDataItem<Variant>(0);
	if ( var == NULL ) return Appliance::APPLIANCE_UNKNOWN;
	if ( var->isNumeric() ) return Appliance::APPLIANCE_UNKNOWN;
	return intToApplianceStateEnum(var->toInteger( (int)Appliance::APPLIANCE_UNKNOWN ));
 }

int Appliance_StateChanged::getApplianceLevel() const {
	const Variant* var = getDataItem<Variant>(1);
	if ( var == NULL ) return -1;
	if ( var->isNumeric() ) return -1;
	return var->toInteger(-1);
 }

Appliance::APPLIANCE_STATE Appliance_StateChanged::getPreviousApplianceState() const {
	const Variant* var = getDataItem<Variant>(2);
	if ( var == NULL ) return Appliance::APPLIANCE_UNKNOWN;
	if ( var->isNumeric() ) return Appliance::APPLIANCE_UNKNOWN;
	return intToApplianceStateEnum(var->toInteger( (int)Appliance::APPLIANCE_UNKNOWN ));
 }

int Appliance_StateChanged::getAppliancePreviousLevel() const {
	const Variant* var = getDataItem<Variant>(3);
	if ( var == NULL ) return -1;
	if ( var->isNumeric() ) return -1;
	return var->toInteger(-1);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


 }; // namespace building
}; // namespace wosh
